package com.relax.mybatis.encrypt.strategy;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.BiConsumer;

import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.springframework.util.CollectionUtils;

import com.relax.mybatis.annotation.EncryptField;
import com.relax.mybatis.model.FieldCacheProperty;
import com.relax.mybatis.util.PluginUtil;

/**
 * @author relax
 * @date 2022/07/05
 * @desc 缓存class对应的加密字段，提高运行效率
 */
public class EncryptFieldHolder {

    private static Map<Class<?>, List<FieldCacheProperty>> CLASS_FIELD_MAP = new ConcurrentHashMap<>(16);

    private static Set<Class<?>> EXCLUDE_CLASS = new CopyOnWriteArraySet<>();

    public static List<FieldCacheProperty> getFieldCacheProperty(Class<?> clazz) {
        if (EXCLUDE_CLASS.contains(clazz))
            return null;
        List<FieldCacheProperty> list = CLASS_FIELD_MAP.get(clazz);
        if (null == list)
            if (clazz.isAssignableFrom(HashMap.class)) {
                EXCLUDE_CLASS.add(clazz);
            } else {
                list = new ArrayList<>();
                List<Field> list1 = PluginUtil.getDeclaredFields(clazz);
                for (Field field : list1) {
                    EncryptField encryptField = null;
                    encryptField = field.getAnnotation(EncryptField.class);
                    if (null != encryptField && !field.getType().isAssignableFrom(String.class)) {
                        throw new IllegalArgumentException(String.format(
                            "annotation `@EncryptField` is not allowed to apply at type %s, only for String. see at %s.%s",
                            field.getType().getTypeName(), clazz.getTypeName(), field.getName()));
                    }
                    if (null != encryptField)
                        list.add(new FieldCacheProperty().setFieldName(field.getName()).setEncryptField(encryptField));
                }
                if (list.isEmpty()) {
                    EXCLUDE_CLASS.add(clazz);
                } else {
                    CLASS_FIELD_MAP.put(clazz, list);
                }
            }
        return list;
    }

    public static boolean handleObject(Configuration configuration, Object object,
        BiConsumer<MetaObject, FieldCacheProperty> consumer) {
        List<FieldCacheProperty> list = getFieldCacheProperty(object.getClass());
        if (!CollectionUtils.isEmpty(list)) {
            MetaObject metaObject = configuration.newMetaObject(object);
            list.stream().forEach(fieldCacheProperty -> consumer.accept(metaObject, fieldCacheProperty));
            return true;
        }
        return false;
    }

}
